﻿using System;

namespace Silver3D.Graphics
{
    public class PrimitiveSet
    {
        private int[] indices;

        public PrimitiveType PrimitiveType { get; private set; }

        public PrimitiveRenderType PrimitiveRenderType { get; private set; }
        public int PrimitiveCount { get; private set; }
        public int IndexCount { get; private set; }

        public VertexBuffer Vertices { get; private set; }

        public PrimitiveSet()
        {
            Vertices = new VertexBuffer();
            SetPoints();
        }

        public Vertex this[int index]
        {
            get { return Vertices[indices[index]]; }
        }

        public void SetPoints()
        {
            PrimitiveType = PrimitiveType.POINTS;
            PrimitiveRenderType = PrimitiveRenderType.POINTS;
            
            PrimitiveCount = Vertices.Count;
            IndexCount = PrimitiveCount;

            if (Vertices.Count > 0)
            {
                indices = new int[PrimitiveCount];
                for (int i = 0; i < PrimitiveCount; ++i) indices[i] = i;
            }
            else
                indices = null;
        }

        private void ValidateIndices(int[] indices, int multipleOf, int minLen)
        {
            if (indices == null)
                throw new ArgumentNullException("indices");

            if (indices.Length < minLen)
                throw new Silver3DException("indices.Length must be at least {0} (length is {1})", minLen, indices.Length);

            if (multipleOf > 1 && indices.Length % multipleOf != 0)
                throw new Silver3DException("indices.Length must be a multiple of {0} (length is {1})", multipleOf, indices.Length);

            int maxIndex = Vertices.Count - 1;
            foreach (int idx in indices)
            {
                if (idx < 0)
                    throw new Silver3DException("index cannot be negative (value is {0})", idx);
                if (idx > maxIndex)
                    throw new Silver3DException("index cannot be greater than {0} (value is {1})", maxIndex, idx);
            }
        }

        private void CopyIndices(int[] indices, bool doCopy)
        {
            if (doCopy)
            {
                this.indices = new int[indices.Length];
                Array.Copy(indices, this.indices, indices.Length);
            }
            else
            {
                this.indices = indices;
            }
            IndexCount = this.indices.Length;
        }

        public void SetLines(int[] indices, bool copyIndices)
        {
            ValidateIndices(indices, 2, 2);

            PrimitiveType = PrimitiveType.LINES;
            PrimitiveRenderType = PrimitiveRenderType.LINES;

            CopyIndices(indices, copyIndices);
        }

        public void SetLineStrip(int[] indices)
        {
            ValidateIndices(indices, 1, 2);

            PrimitiveType = PrimitiveType.LINE_STRIP;
            PrimitiveRenderType = PrimitiveRenderType.LINES;

            PrimitiveCount = indices.Length - 1;
            this.indices = new int[PrimitiveCount * 2];
            IndexCount = this.indices.Length;

            for (int line = 0, offset = 0; line < PrimitiveCount; ++line)
            {
                this.indices[offset++] = indices[line];
                this.indices[offset++] = indices[line + 1];
            }
        }

        public void SetLineLoop(int[] indices)
        {
            ValidateIndices(indices, 1, 3);

            PrimitiveType = PrimitiveType.LINE_LOOP;
            PrimitiveRenderType = PrimitiveRenderType.LINES;

            PrimitiveCount = indices.Length;
            this.indices = new int[PrimitiveCount * 2];
            IndexCount = this.indices.Length;

            int offset = 0, line = 0;
            for (; line < PrimitiveCount - 1; ++line)
            {
                this.indices[offset++] = indices[line];
                this.indices[offset++] = indices[line + 1];
            }
            // close the loop
            this.indices[offset++] = indices[line];
            this.indices[offset++] = indices[0];
        }

        public void SetTriangles(int[] indices, bool copyIndices)
        {
            ValidateIndices(indices, 3, 3);

            PrimitiveType = PrimitiveType.TRIANGLES;
            PrimitiveRenderType = PrimitiveRenderType.TRIANGLES;
            PrimitiveCount = indices.Length / 3;

            CopyIndices(indices, copyIndices);
        }

        public void SetTriangleStrip(int[] indices)
        {
            ValidateIndices(indices, 1, 3);

            PrimitiveType = PrimitiveType.TRIANGLE_STRIP;
            PrimitiveRenderType = PrimitiveRenderType.TRIANGLES;
            PrimitiveCount = indices.Length - 2;

            this.indices = new int[PrimitiveCount * 3];
            IndexCount = this.indices.Length;

            for (int n = 0, offset = 0; n < PrimitiveCount; n += 2, offset += 6)
            {
                // odd n (n + 1 is odd)
                this.indices[offset] = indices[n];
                this.indices[offset + 1] = indices[n + 1];
                this.indices[offset + 2] = indices[n + 2];
            }
            for (int n = 1, offset = 3; n < PrimitiveCount; n += 2, offset += 6)
            {
                // even n (n + 1 is even)
                this.indices[offset] = indices[n + 1];
                this.indices[offset + 1] = indices[n];
                this.indices[offset + 2] = indices[n + 2];
            }
        }

        public void SetTriangleFan(int[] indices)
        {
            ValidateIndices(indices, 1, 3);

            PrimitiveType = PrimitiveType.TRIANGLE_FAN;
            PrimitiveRenderType = PrimitiveRenderType.TRIANGLES;
            PrimitiveCount = indices.Length - 2;

            this.indices = new int[PrimitiveCount * 3];
            IndexCount = this.indices.Length;

            for (int n = 0, offset = 0; n < PrimitiveCount; ++n)
            {
                this.indices[offset++] = indices[0];
                this.indices[offset++] = indices[n + 1];
                this.indices[offset++] = indices[n + 2];
            }
        }

        public void SetQuads(int[] indices)
        {
            ValidateIndices(indices, 4, 4);

            PrimitiveType = PrimitiveType.QUADS;
            PrimitiveRenderType = PrimitiveRenderType.TRIANGLES;
            int quadCount = indices.Length / 4;
            PrimitiveCount = quadCount * 2;

            this.indices = new int[quadCount * 6];
            IndexCount = this.indices.Length;

            for (int n = 0, offset = 0; n < quadCount; ++n)
            {
                // tri 1
                this.indices[offset++] = indices[n];
                this.indices[offset++] = indices[n + 1];
                this.indices[offset++] = indices[n + 2];
                // tri 2
                this.indices[offset++] = indices[n];
                this.indices[offset++] = indices[n + 2];
                this.indices[offset++] = indices[n + 3];
            }
        }

        public void SetQuadStrip(int[] indices)
        {
            ValidateIndices(indices, 2, 4);

            PrimitiveType = PrimitiveType.QUAD_STRIP;
            PrimitiveRenderType = PrimitiveRenderType.TRIANGLES;
            int quadCount = (indices.Length / 2) - 1;
            PrimitiveCount = quadCount * 2;

            this.indices = new int[quadCount * 6];
            IndexCount = this.indices.Length;

            for (int i = 0, offset = 0; i < quadCount; ++i)
            {
                int nn = (2 * (i + 1)) - 1;
                // tri 1
                this.indices[offset++] = indices[nn - 1];
                this.indices[offset++] = indices[nn];
                this.indices[offset++] = indices[nn + 2];
                // tri 2
                this.indices[offset++] = indices[nn - 1];
                this.indices[offset++] = indices[nn + 2];
                this.indices[offset++] = indices[nn + 1];
            }
        }

        public void SetPolygon(int[] indices)
        {
            ValidateIndices(indices, 1, 3);

            throw new NotImplementedException();
        }
    }
}
